// [[ OnflowGenerator File UUID: d1cd3ba8c20548d29046f7da0d1a1af8  ]]
package com.itdct.onflow.system.account.service

import cn.hutool.core.lang.UUID
import cn.hutool.crypto.digest.BCrypt
import com.alibaba.fastjson2.toJSONString
import com.baomidou.mybatisplus.extension.kotlin.KtQueryWrapper
import com.itdct.onflow.core.constant.CommonString
import com.itdct.onflow.core.constant.LoginTerminalType
import com.itdct.onflow.core.constant.ReturnCode
import com.itdct.onflow.core.extend.*
import com.itdct.onflow.core.util.KBeanCopyUtil
import com.itdct.onflow.core.util.ProjectRsaUtil
import com.itdct.onflow.core.util.RedisReentrantLock
import com.itdct.onflow.core.vo.KeyLabelValueVo
import com.itdct.onflow.core.vo.RespPageVo
import com.itdct.onflow.core.vo.RespVo
import com.itdct.onflow.system.account.constant.SysAccountRedisKey
import com.itdct.onflow.system.account.entity.SysAccountEntity
import com.itdct.onflow.system.account.mapper.SysAccountMapper
import com.itdct.onflow.system.account.qo.*
import com.itdct.onflow.system.account.vo.SysAccountDetailVo
import com.itdct.onflow.system.account.vo.SysAccountListVo
import com.itdct.onflow.system.account.vo.SysAccountLoginVo
import com.itdct.onflow.system.account.vo.SysAccountMyInfoVo
import com.itdct.onflow.system.common.base.BaseSysService
import com.itdct.onflow.system.common.base.LoginDTO
import com.itdct.onflow.system.common.context.Context
import com.itdct.onflow.system.common.extend.dict
import com.itdct.onflow.system.log.entity.SysLoginLogEntity
import com.itdct.onflow.system.log.mapper.SysLoginLogMapper
import com.itdct.onflow.system.role.entity.SysRoleAccountEntity
import com.itdct.onflow.system.role.mapper.SysRoleAccountMapper
import com.itdct.onflow.system.role.mapper.SysRoleMapper
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.util.*
import kotlin.random.Random

/**
 * @author DCTANT
 * @date 2025-09-01 15:11:24
 * @version 1.0
 * @description 系统账户服务
 */
@Service
class SysAccountService(
    val sysAccountMapper: SysAccountMapper,
    val projectRsaUtil: ProjectRsaUtil,
    val roleAccountMapper: SysRoleAccountMapper,
    val sysRoleMapper: SysRoleMapper,
    val rsaUtil: ProjectRsaUtil,
    val sysRoleAccountMapper: SysRoleAccountMapper,
    val sysLoginLogMapper: SysLoginLogMapper,
    val redisReentrantLock: RedisReentrantLock
) : BaseSysService() {

    @Transactional(rollbackFor = [Exception::class])
    fun add(qo: SysAccountEditQo, context: Context): RespVo<Long> {
        val addEntity = qo.toEntity(SysAccountEntity::class.java)
        val password = qo.password!!
        val decryptPassword = rsaUtil.privateKeyDecrypt(password)
        addEntity.password = BCrypt.hashpw(decryptPassword)
        addEntity.rsaPassword = projectRsaUtil.publicKeyEncrypt(decryptPassword)
        addEntity.locked = false
        addEntity.loginCount = 0
        addEntity.status = 0
        addEntity.registerDate = Date()
        qo.nickName?.also {
            addEntity.nickName = it
        } ?: run {
            addEntity.nickName = addEntity.name
        }
        var uid = redisUtil.incr(SysAccountRedisKey.UID_KEY, Random.nextLong(10, 100))
        if (uid < 10000) {
            uid = 10000L
            redisUtil.set(SysAccountRedisKey.UID_KEY, uid)
        }
        addEntity.uid = uid.toString()

        val id = sysAccountMapper.insertEx(addEntity)

        qo.roleId?.also {
            insertRoleAccountEntity(id, it)
        }
        return success(id)
    }

    private fun insertRoleAccountEntity(id: Long, it: Long) {
        val roleAccountEntity = SysRoleAccountEntity()
        roleAccountEntity.accountId = id
        roleAccountEntity.roleId = it
        roleAccountEntity.platformType = "onflow-backend"
        roleAccountMapper.insertEx(roleAccountEntity)
    }

    @Transactional(rollbackFor = [Exception::class])
    fun update(qo: SysAccountEditQo, context: Context): RespVo<Unit> {
        val updateEntity = sysAccountMapper.selectByIdEx(qo.id)
        qo.injectValue(updateEntity)

        val password = qo.password
        if (password != null) {
            val decryptPassword = rsaUtil.privateKeyDecrypt(password)
            updateEntity.password = BCrypt.hashpw(decryptPassword)
            updateEntity.rsaPassword = projectRsaUtil.publicKeyEncrypt(decryptPassword)
        }

        sysAccountMapper.updateEx(updateEntity)

        qo.roleId?.also {
            val ktQueryWrapper = KtQueryWrapper(SysRoleAccountEntity::class.java)
            ktQueryWrapper.eq(SysRoleAccountEntity::accountId, qo.id)
            roleAccountMapper.delete(ktQueryWrapper)
            insertRoleAccountEntity(updateEntity.id!!, it)
        }

        return success()
    }

    fun delete(qo: SysAccountEditQo, context: Context): RespVo<Unit> {
        val ids = qo.ids ?: fail(ReturnCode.LACK_PARAM, CommonString.REQUIRE_DELETE_ID_LIST)
        val deleteEntities = sysAccountMapper.selectByIdsEx(ids)
        sysAccountMapper.deleteBatchEx(deleteEntities)
        return success()
    }

    fun detail(qo: SysAccountSearchQo, context: Context): RespVo<SysAccountDetailVo> {
        val id = qo.id ?: fail(ReturnCode.LACK_PARAM, CommonString.REQUIRE_ID)
        val vo = sysAccountMapper.findById(id) ?: fail(CommonString.ENTITY_NOT_FOUND_USAGE("系统账户", id))
        return success(vo.dict())
    }

    fun page(qo: SysAccountSearchQo, context: Context): RespPageVo<SysAccountListVo> {
        startPage(context)
        val pageData = sysAccountMapper.findByList(qo)
        return successPage(pageData.dict())
    }

    fun login(qo: SysAccountLoginQo, context: Context): RespVo<SysAccountLoginVo> {
        val ipAddress = context.ipAddress
        val isLock = redisUtil.get(SysAccountRedisKey.LOGIN_IP_LOCK(ipAddress)) as Boolean?
        isLock?.also {
            if (it) {
                fail("该ip登录已锁定，请稍后再试")
            }
        }
        val loginConfig = onflowConfig.login

        val password = qo.password
        val name = qo.name

        // INFO: Zhouwx: 2025/8/22 解密前端传过来的密码，前端使用RSA公钥加密密码
//        val decryptPassword = projectRsaUtil.privateKeyDecrypt(password) ?: fail("用户名或手机号不存在，或者密码填写错误")
        val decryptPassword = password

        val accountEntity: SysAccountEntity = sysAccountMapper.findByNameOrMobile(name) ?: fail("用户名或手机号不存在，或者密码填写错误")
        if (accountEntity.locked) {
            addLoginLog(accountEntity, ipAddress, false, "该账户已锁定，请联系管理员")
            fail("该账户已锁定，请联系管理员")
        }

        if (!BCrypt.checkpw(decryptPassword, accountEntity.password)) {
            // INFO: Zhouwx: 2025/8/22 密码不正确
            val failCount = redisUtil.get(SysAccountRedisKey.LOGIN_FAIL_RECORD(ipAddress)) as Int?
            if (failCount == null) {
                redisUtil.set(SysAccountRedisKey.LOGIN_FAIL_RECORD(ipAddress), 1, loginConfig.lockTime.toLong())
            } else {
                if (failCount >= loginConfig.repeatTimes) {
                    redisUtil.set(SysAccountRedisKey.LOGIN_IP_LOCK(ipAddress), true, loginConfig.lockTime.toLong())
                    accountEntity.lockStartTime = Date()
                    accountEntity.lockEndTime = Date(System.currentTimeMillis() + loginConfig.lockTime * 1000)
                    sysAccountMapper.updateEx(accountEntity)
                    fail("该ip登录已锁定，请稍后再试")
                } else {
                    redisUtil.set(SysAccountRedisKey.LOGIN_FAIL_RECORD(ipAddress), failCount + 1, loginConfig.lockTime.toLong())
                }
            }
            addLoginLog(accountEntity, ipAddress, false, "密码填写错误")
            fail("用户名或手机号不存在，或者密码填写错误")
        }

        val sysAccountLoginVo = SysAccountLoginVo()
        KBeanCopyUtil.injectValue(accountEntity, sysAccountLoginVo)
        val token = UUID.fastUUID().toString(true)
        val sysRoleAccountEntities = sysRoleAccountMapper.findByAccountId(accountEntity.id!!)
        if (sysRoleAccountEntities.isEmpty()) {
            error("该账户没有角色，请联系管理员")
        }
        val roleId = sysRoleAccountEntities[0].roleId
        sysAccountLoginVo.roleId = roleId
        val roleEntity = sysRoleMapper.selectByIdEx(sysAccountLoginVo.roleId)
        sysAccountLoginVo.roleName = roleEntity.name
        sysAccountLoginVo.token = token

        val loginDTO = LoginDTO()
        loginDTO.accountId = accountEntity.id!!
        loginDTO.accountUid = accountEntity.uid
        loginDTO.accountName = accountEntity.name
        loginDTO.accountNickName = accountEntity.nickName
        loginDTO.loginTerminalType = LoginTerminalType.WEB
        loginDTO.platformType = onflowConfig.platformType
        loginDTO.expireTime = Date(System.currentTimeMillis() + loginConfig.tokenValidTime).time
        loginDTO.loginIp = ipAddress
        loginDTO.token = token
        loginDTO.roleId = roleId
        loginDTO.roleName = roleEntity.name
        loginDTO.superAdmin = roleEntity.superAdmin
        context.superAdmin = roleEntity.superAdmin

        redisUtil.set(SysAccountRedisKey.LOGIN_TOKEN(token), loginDTO.toJSONString(), loginConfig.tokenValidTime)
        redisUtil.lSet(SysAccountRedisKey.LOGIN_ID(accountEntity.id!!, "admin"), loginDTO.toJSONString(), loginConfig.tokenValidTime)
        redisUtil.del(SysAccountRedisKey.LOGIN_FAIL_RECORD(ipAddress))

        // INFO: Zhouwx: 2025/9/28 设置token刷新间隔时间
        redisReentrantLock.tryLock(SysAccountRedisKey.LOGIN_TOKEN_REFRESH_INTERVAL(token), 0, onflowConfig.login.tokenRefreshIntervalTime)

        val nowLoginIp = accountEntity.nowLoginIp
        val nowLoginDate = accountEntity.nowLoginDate

        if (nowLoginIp != null) {
            accountEntity.lastLoginIp = nowLoginIp
        }

        if (nowLoginDate != null) {
            accountEntity.lastLoginDate = nowLoginDate
        }
        accountEntity.nowLoginIp = ipAddress
        accountEntity.nowLoginDate = Date()
        val loginCount = accountEntity.loginCount
        accountEntity.loginCount = loginCount + 1
        sysAccountMapper.updateEx(accountEntity)

        addLoginLog(accountEntity, ipAddress, true, "")

        return success(sysAccountLoginVo)
    }

    private fun addLoginLog(accountEntity: SysAccountEntity, ipAddress: String, success: Boolean, failReason: String) {
        val sysLoginLogEntity = SysLoginLogEntity()
        sysLoginLogEntity.accountId = accountEntity.id!!
        sysLoginLogEntity.platformType = onflowConfig.platformType
        sysLoginLogEntity.ipAddress = ipAddress
        if (success) {
            sysLoginLogEntity.result = 10
        } else {
            sysLoginLogEntity.result = 0
        }
        sysLoginLogEntity.failReason = failReason
        sysLoginLogEntity.loginTime = Date()
        sysLoginLogMapper.insertEx(sysLoginLogEntity)
    }

    fun logout(context: Context): RespVo<SysAccountLoginVo> {
        val loginDTO = context.loginDTO ?: fail(ReturnCode.UNAUTHORIZED, CommonString.NEED_LOGIN)
        val token = loginDTO.token
        redisUtil.del(SysAccountRedisKey.LOGIN_TOKEN(token))
        val accountId = loginDTO.accountId ?: fail(CommonString.NEED_LOGIN)
        redisUtil.lRemove(SysAccountRedisKey.LOGIN_ID(accountId, "admin"), 0, loginDTO.toJSONString())
        return success()
    }

    fun getMyInfo(context: Context): RespVo<SysAccountMyInfoVo> {
        val id = context.accountId ?: fail(ReturnCode.UNAUTHORIZED, CommonString.NEED_LOGIN)
        val vo = sysAccountMapper.findMyInfo(id) ?: fail(CommonString.ENTITY_NOT_FOUND_USAGE("系统账户", id))
        return success(vo.dict())
    }

    @Transactional(rollbackFor = [Exception::class])
    fun updateMyInfo(qo: SysAccountMyInfoQo, context: Context): RespVo<Unit> {
        val id = context.accountId ?: fail(ReturnCode.UNAUTHORIZED, CommonString.NEED_LOGIN)
        val updateEntity = sysAccountMapper.selectByIdEx(id)
        qo.injectValue(updateEntity)

        sysAccountMapper.updateEx(updateEntity)
        return success()
    }

    @Transactional(rollbackFor = [Exception::class])
    fun updatePassword(qo: SysAccountUpdatePasswordQo, context: Context): RespVo<Unit> {
        val accountId = context.accountId ?: fail(ReturnCode.UNAUTHORIZED, CommonString.NEED_LOGIN)
        val accountEntity = sysAccountMapper.selectByIdEx(accountId)
        val originPassword = qo.originPassword ?: fail(ReturnCode.LACK_PARAM, "请输入原始密码")
        val originPasswordDecrypt = projectRsaUtil.privateKeyDecrypt(originPassword)
        if (!BCrypt.checkpw(originPasswordDecrypt, accountEntity.password)) {
            fail("原始密码错误")
        }

        val newPassword = qo.newPassword ?: fail(ReturnCode.LACK_PARAM, "请输入新密码")
        val newPasswordDecrypt = projectRsaUtil.privateKeyDecrypt(newPassword)
        if (newPasswordDecrypt.length < 6) {
            fail("密码长度不能小于6位")
        }
        accountEntity.password = BCrypt.hashpw(newPasswordDecrypt, BCrypt.gensalt())
        accountEntity.rsaPassword = newPassword
        sysAccountMapper.updateEx(accountEntity)
        return success()
    }

// [[ OnflowGenerator Range (- selectFunctions -) Start ]]

    fun selectNameList(qo: SysAccountSearchQo, context: Context): RespVo<List<KeyLabelValueVo>> {
        val vos = sysAccountMapper.selectNameList(qo)
        return success(vos)
    }
// [[ OnflowGenerator Range (- selectFunctions -) End ]]
}